package edu.tongji.structureplayer.structure
{
    import edu.tongji.structureplayer.core.ErrorNames;
    
    import org.as3commons.collections.LinkedList;
    import org.as3commons.collections.framework.ILinkedList;
    import org.as3commons.collections.framework.ILinkedListIterator;
    import org.as3commons.lang.StringBuffer;
    
    public class WellBehavedStructure
    {
        private var _nodes:ILinkedList = new LinkedList();
        private var _elements:ILinkedList = new LinkedList();
        private var _properties:ILinkedList = new LinkedList();
        private var _materialProperties:ILinkedList = new LinkedList();
        private var _sectionConstants:ILinkedList = new LinkedList();
        private var _piecewiseFunctions:ILinkedList = new LinkedList();
        private var _connections:ILinkedList = new LinkedList();
        private var _constrains:ILinkedList = new LinkedList();
        private var _keyPoints:ILinkedList = new LinkedList();
        private var _loads:ILinkedList = new LinkedList();
        
        /**
        * Adds a Node by its name, x position and y position.
        * 
        * <p>Errors occur when Node with `name' exists or new position overlaps the
        * existing Nodes.</p> 
        */
        public function addNode(name:String, x:Number, y:Number):void
        {
            var lli:ILinkedListIterator = nodes.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var n:Node = lli.current as Node;
                if (n.name==name)
                    throw new Error(ErrorNames.NODE_NAME_EXISTS + name);
                if (n.x==x && n.y==y)
                    throw new Error(ErrorNames.NODE_POSITION_OVERLAPS + n.name);
            }
            nodes.addLast(new Node(name, x, y));
        }
        
        public function addKeyPoint(name:String, position:Number, strength:Number, 
                                    direction:Number):void
        {
            var lli:ILinkedListIterator = keyPoints.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var p:KeyPoint = lli.current as KeyPoint;
                if (p.name==name)
                    throw new Error(ErrorNames.KEYPOINT_NAME_EXISTS + name);
            }
            keyPoints.addLast(new KeyPoint(name, position, strength, direction));
        }
        
        public function newALoad(name:String, type:String):Load
        {
            var lli:ILinkedListIterator = loads.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var l:Load = lli.current as Load;
                if (l.name==name)
                    throw new Error(ErrorNames.LOAD_NAME_EXISTS + name);
            }
            return new Load(name, type);
        }
        
        public function addLoadOnNode(name:String, nodeName:String, strength:Number, 
                                    direction:Number, type:String):void
        {
            var load:Load = newALoad(name, type);
            var node:Node = findNodeByName(nodeName);
            load.setForce(node, strength, direction);
            loads.addLast(load);
        }
        
        public function addLoadOnElement(name:String, elementName:String, 
                                         skpName:String, ekpName:String, 
                                         type:String):void
        {
            var load:Load = newALoad(name, type);
            var startKeyPoint:KeyPoint = findKeyPointByName(skpName);
            var endKeyPoint:KeyPoint = findKeyPointByName(ekpName);
            var element:Element = findElementByName(elementName);
            load.setKeyPoints(element, startKeyPoint, endKeyPoint);
            loads.addLast(load);
        }
        
        /**
        * Adds a element. 
        * 
        * <p>Error occurs when nodes are not found or property is not found or
        * connections are not found or element overlaps with an existing element.
        * </p>
        */
        public function addElement(name:String, startNodeName:String, endNodeName:String, propertyName:String,
                                    startConnName:String, endConnName:String):void
        {
            var startNode:Node = findNodeByName(startNodeName);
            var endNode:Node = findNodeByName(endNodeName);
            var property:Property = findPropertyByName(propertyName);
            var startConn:Connection = findConnectionByName(startConnName);
            var endConn:Connection = findConnectionByName(endConnName);
            
            var lli:ILinkedListIterator = elements.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var e:Element = lli.current as Element;
                if (e.name==name)
                    throw new Error(ErrorNames.ELEMENT_NAME_EXISTS + name);
                if (e.overlap(startNode, endNode, property, startConn, endConn))
                    throw new Error(ErrorNames.ELEMENT_OVERLAPS + e.name);
            }
            elements.addLast(new Element(name, startNode, endNode, property, 
            startConn, endConn));
        }
        
        /**
        * Adds a piecewise function by specifying a series of inflection poins.
        * Between every two adjacent points, a linear function is defined. The slope
        * of this linear function can be accessed by specifying the x-coordinate.
        * 
        * xPos is the series of x coordinates.
        * yPos is the series of y coordinates.
        * 
        * For example, specifying
        * 
        *   xPos = [-2,0,3,5];
        *   yPos = [-4,0,5,12];
        * 
        * will generate a function as following
        * 
        *          | 0         x < -2,
        *          | 2x        -2 <= x < 0,
        *   f(x) = | (5/3)x    0 <= x < 3,
        *          | (12/5)x   3 <= x < 5,
        *          | 0         x >= 5
        * 
        * Error occurs when Function with `name' exists or the length of xPos does
        * not equal the length of yPos or xPos, yPos overlap with an existing
        * Function.
        */
        public function addPiecewiseFunction(name:String, xPos:Array, yPos:Array):void
        {
            var lli:ILinkedListIterator = piecewiseFunctions.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var f:PiecewiseFunction = lli.current as PiecewiseFunction;
                if (f.name==name)
                    throw new Error(ErrorNames.FUNCTION_NAME_EXISTS + name);
                if (f.overlap(xPos, yPos))
                    throw new Error(ErrorNames.FUNCTION_OVERLAPS + f.name);
            }
            piecewiseFunctions.addLast(new PiecewiseFunction(name, xPos, yPos));
        }
        
        /**
        * Adds a connection. The piecewise function is optional.
        * 
        * Error occurs when the connection with `name' exists or connection overlaps
        * with an existing connection.
        */
        public function addConnection(name:String, xEnable:Boolean, yEnable:Boolean,
                                      thetaEnable:Boolean, funcName:String=null):void
        {
            var func:PiecewiseFunction;
            if (null==funcName)
                func = null;
            else
                func = findPiecewiseFunctionByName(funcName);
            
            var lli:ILinkedListIterator = connections.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var c:Connection = lli.current as Connection;
                if (c.name==name)
                    throw new Error(ErrorNames.CONNECTION_NAME_EXISTS + name);
                if (c.overlap(xEnable, yEnable, thetaEnable, func))
                    throw new Error(ErrorNames.CONNECTION_OVERLAPS + c.name);
            }
            
            connections.addLast(new Connection(name, xEnable, yEnable, thetaEnable, func));
        }
        
        public function addConstrain(name:String, nodeName:String, connName:String, theta:Number=0):void
        {
            var node:Node = findNodeByName(nodeName);
            var conn:Connection = findConnectionByName(connName);
            
            var lli:ILinkedListIterator = constrains.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var c:Constrain = lli.current as Constrain;
                if (c.name==name)
                    throw new Error(ErrorNames.CONSTRAIN_NAME_EXISTS + name);
                if (c.overlap(node))
                    throw new Error(ErrorNames.CONSTRAIN_OVERLAPS + c.name);
            }
            
            constrains.addLast(new Constrain(name, node, conn, theta));
        }
        
        /**
        * Adds a property by its name, material property name and section constant
        * name. The material property and section constant must be defined before
        * a property is added.
        * 
        * Error occurs when property with `name' exists or `materialPropertyName'
        * does not exist or `sectionConstantName' does not exist or materialPropertyName
        * and sectionConstantName overlaps with one existing property.
        */
        public function addProperty(name:String, materialPropertyName:String, sectionConstantName:String):void
        {
            var mp:MaterialProperty = findMaterialPropertyByName(materialPropertyName);
            var sc:SectionConstant = findSectionConstantByName(sectionConstantName);
            var lli:ILinkedListIterator = properties.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var p:Property = lli.current as Property;
                if (p.name==name)
                    throw new Error(ErrorNames.PROPERTY_NAME_EXISTS + name);
                if (p.overlap(mp, sc))
                    throw new Error(ErrorNames.PROPERTY_OVERLAPS + p.name);
            }
            properties.addLast(new Property(name, mp, sc));
        }
        
        /**
        * Adds a material property by its name, elastic modulus and poisson ratio.
        * 
        * Error occurs when material property with `name' exists or parameters
        * elastic modulus and poisson ratio overlaps one existing material property.
        */
        public function addMaterialProperty(name:String, elasticModulus:Number, poissonRatio:Number):void
        {
            var lli:ILinkedListIterator = materialProperties.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var p:MaterialProperty = lli.current as MaterialProperty;
                if (p.name==name)
                    throw new Error(ErrorNames.MATERIAL_PROPERTY_NAME_EXISTS + name);
                if (p.elasticModulus==elasticModulus && p.poissonRatio==poissonRatio)
                    throw new Error(ErrorNames.MATERIAL_PROPERTY_PARAMETERS_OVERLAPS + p.name
                        + "(" + elasticModulus + ", " + poissonRatio + ")");
            }
            materialProperties.addLast(new MaterialProperty(name, elasticModulus, poissonRatio));
            
        }
        
        /**
         * Adds a section constant by its name, cross area and second moment of area.
         * 
         * Error occurs when section constant with `name' exists or parameters
         * cross area and second moment of area overlaps one existing material property.
         */
        public function addSectionConstant(name:String, crossArea:Number, secondMomentOfArea:Number):void
        {
            var lli:ILinkedListIterator = sectionConstants.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var s:SectionConstant = lli.current as SectionConstant;
                if (s.name==name)
                    throw new Error(ErrorNames.SECTION_CONSTANT_NAME_EXISTS + name);
                if (s.crossArea==crossArea && s.secondMomentOfArea==secondMomentOfArea)
                    throw new Error(ErrorNames.SECTION_CONSTANT_PARAMETERS_OVERLAPS + s.name
                        + "(" + crossArea + ", " + secondMomentOfArea + ")");
            }
            sectionConstants.addLast(new SectionConstant(name, crossArea, secondMomentOfArea));
            
        }
        
        public function debugPrint():void
        {
            trace("[WellBehavedStructure]");
            var i1:ILinkedListIterator = nodes.iterator() as ILinkedListIterator;
            while (i1.hasNext())
            {
                i1.next();
                (i1.current as Node).debugPrint();
            }
            var i2:ILinkedListIterator = materialProperties.iterator() as ILinkedListIterator;
            while (i2.hasNext())
            {
                i2.next();
                (i2.current as MaterialProperty).debugPrint();
            }
            var i3:ILinkedListIterator = sectionConstants.iterator() as ILinkedListIterator;
            while (i3.hasNext())
            {
                i3.next();
                (i3.current as SectionConstant).debugPrint();
            }
            var i4:ILinkedListIterator = piecewiseFunctions.iterator() as ILinkedListIterator;
            while (i4.hasNext())
            {
                i4.next();
                (i4.current as PiecewiseFunction).debugPrint();
            }
            var i5:ILinkedListIterator = properties.iterator() as ILinkedListIterator;
            while (i5.hasNext())
            {
                i5.next();
                (i5.current as Property).debugPrint();
            }
            var i6:ILinkedListIterator = connections.iterator() as ILinkedListIterator;
            while (i6.hasNext())
            {
                i6.next();
                (i6.current as Connection).debugPrint();
            }
            var i7:ILinkedListIterator = elements.iterator() as ILinkedListIterator;
            while (i7.hasNext())
            {
                i7.next();
                (i7.current as Element).debugPrint();
            }
            var i8:ILinkedListIterator = constrains.iterator() as ILinkedListIterator;
            while (i8.hasNext())
            {
                i8.next();
                (i8.current as Constrain).debugPrint();
            }
            var i9:ILinkedListIterator = keyPoints.iterator() as ILinkedListIterator;
            while (i9.hasNext())
            {
                i9.next();
                (i9.current as KeyPoint).debugPrint();
            }
            var i10:ILinkedListIterator = loads.iterator() as ILinkedListIterator;
            while (i10.hasNext())
            {
                i10.next();
                (i10.current as Load).debugPrint();
            }
            
            trace();
        }
        
        /**
        * Finds a Node by name in nodes list.
        * 
        * Error occurs when Node is not found.
        */
        public function findNodeByName(name:String):Node
        {
            var lli:ILinkedListIterator = nodes.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as Node).name == name)
                    return lli.current as Node;
            }
            throw new Error(ErrorNames.NODE_NOT_FOUND + name);
        }
        
        public function findElementByName(name:String):Element
        {
            var lli:ILinkedListIterator = elements.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as Element).name == name)
                    return lli.current as Element;
            }
            throw new Error(ErrorNames.ELEMENT_NOT_FOUND + name);
        }
        
        public function findKeyPointByName(name:String):KeyPoint
        {
            var lli:ILinkedListIterator = keyPoints.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as KeyPoint).name == name)
                    return lli.current as KeyPoint;
            }
            throw new Error(ErrorNames.KEYPOINT_NOT_FOUND + name);
        }
        
        public function findConnectionByName(name:String):Connection
        {
            var lli:ILinkedListIterator = connections.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as Connection).name == name)
                    return lli.current as Connection;
            }
            throw new Error(ErrorNames.CONNECTION_NOT_FOUND + name);
        }
        
        public function findMaterialPropertyByName(name:String):MaterialProperty
        {
            var lli:ILinkedListIterator = materialProperties.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as MaterialProperty).name == name)
                    return lli.current as MaterialProperty;
            }
            throw new Error(ErrorNames.MATERIAL_PROPERTY_NOT_FOUND + name);
        }
        
        public function findSectionConstantByName(name:String):SectionConstant
        {
            var lli:ILinkedListIterator = sectionConstants.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as SectionConstant).name == name)
                    return lli.current as SectionConstant;
            }
            throw new Error(ErrorNames.SECTION_CONSTANT_NOT_FOUND + name);
        }
        
        public function findPiecewiseFunctionByName(name:String):PiecewiseFunction
        {
            var lli:ILinkedListIterator = piecewiseFunctions.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as PiecewiseFunction).name == name)
                    return lli.current as PiecewiseFunction;
            }
            throw new Error(ErrorNames.FUNCTION_NOT_FOUND + name);
        }
        
        public function findPropertyByName(name:String):Property
        {
            var lli:ILinkedListIterator = properties.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                if ((lli.current as Property).name == name)
                    return lli.current as Property;
            }
            throw new Error(ErrorNames.PROPERTY_NOT_FOUND + name);
        }
        /**
         * Finds a Node by index in nodes list.
         * 
         * Error occurs when Node is not found.
         */
        /*public function findNodeByIndex(index:uint):Node
        {
            if (index >= nodes.size || index < 0)
                throw new Error(ErrorNames.NODE_NOT_FOUND);
            var lli:ILinkedListIterator = nodes.iterator() as ILinkedListIterator;
            for (var i:uint=0; i<=index; i++)
                lli.next();
            return lli.current as Node;
        }*/
        
        /**
         * Finds a Node by position in nodes list.
         * 
         * Error occurs when Node is not found.
         */
        /*public function findNodeByPosition(x:Number, y:Number):Node
        {
            var lli:ILinkedListIterator = nodes.iterator() as ILinkedListIterator;
            while (lli.hasNext())
            {
                lli.next();
                var n:Node = lli.current as Node;
                if (n.x == x && n.y == y)
                    return n;
            }
            throw new Error(ErrorNames.NODE_NOT_FOUND);
        }*/
        
        public function numberOfNode():uint
        {
            return nodes.size;
        }

        public function get nodes():ILinkedList
        {
            return _nodes;
        }

        public function get elements():ILinkedList
        {
            return _elements;
        }

        public function get properties():ILinkedList
        {
            return _properties;
        }

        public function get materialProperties():ILinkedList
        {
            return _materialProperties;
        }

        public function get sectionConstants():ILinkedList
        {
            return _sectionConstants;
        }

        public function get piecewiseFunctions():ILinkedList
        {
            return _piecewiseFunctions;
        }

        public function get connections():ILinkedList
        {
            return _connections;
        }

        public function get constrains():ILinkedList
        {
            return _constrains;
        }

        public function get keyPoints():ILinkedList
        {
            return _keyPoints;
        }

        public function get loads():ILinkedList
        {
            return _loads;
        }

        
        /*
        public function removeNodeByName(name:String):void
        {
            nodes.remove(findNodeByName(name));
        }
        
        public function removeNodeByIndex(index:uint):void
        {
            nodes.remove(findNodeByIndex(index));
        }
        
        public function removeNodeByPosition(x:Number, y:Number):void
        {
            nodes.remove(findNodeByPosition(x, y));
        }*/

    }
}